### -*- coding: utf-8 -*- #############################################
# Разработано Паэглит Арвидом (http://arvid.dp.ua/)
# Все права защищены, 2007
#
# Developed by Paeglit Arvid (http://arvid.dp.ua/)
# All right reserved, 2007
#######################################################################
"""Variant class for the Zope 3 based diplom package

$Id: variant.py 66 2007-12-28 23:05:19Z Arvi3D $
"""
__author__  = "Arvid"
__license__ = "GPL"
__version__ = "$Revision: 66 $"
__date__ = "$Date: 2007-12-28 23:05:19 +0000 (Fri, 28 Dec 2007) $"

from zope.schema.fieldproperty import FieldProperty
from zope.interface import implements,implementedBy
from zope.cachedescriptors.property import CachedProperty
from persistent import Persistent
from zope.app.container.contained import Contained

from math import sqrt, exp

from selection import Selection
from interfaces import IVariant

CRITICAL_FREQUENCY = 0.1
CLASS_DISPLACEMENT = 1.0001
CLASS_COUNT_MIN = 7

class Variant(Selection) :
    implements(IVariant)

    variant = ()

    classCount = FieldProperty(IVariant['classCount'])
    front = FieldProperty(IVariant['front'])
    back = FieldProperty(IVariant['back'])
    classLength = FieldProperty(IVariant['classLength'])

    @property
    def vector(self):
        return map(lambda v: v[0], self.variant)

    def __init__(self, vector = []):
        if vector:
            self.regenerateVariant(vector)
        super(Selection, self).__init__()

    def regenerateVariant(self, vector):
        """calculate variant and save in attribute variant"""
        vector = vector[:]
        if not vector:
            return
        vector.sort()

        n = len(vector)
        values = []
        a, b = vector[0], vector[-1]
        #TODO: вынести подсчет количества классов в отдельную функцию и зарегать ее как утилиту
        m = max(CLASS_COUNT_MIN, int(sqrt(n) - 1))
        while(True):
            h = (b - a)/m*CLASS_DISPLACEMENT

            values = []
            for i in range(m):
                values.append([a + i*h, 0, 0, 0])

            for v in vector:
                p = int((v - a)/h)
                values[p][1] += 1
                values[p][2] += 1.0/n

            for v in values:
                if v[2] > CRITICAL_FREQUENCY:
                    m +=1
                    break
            else:
                break

        tSum = 0
        for v in values:
            tSum += v[2]
            v[3] = tSum

        self.variant = values
        self.classCount = m
        self.front = a
        self.back = b
        self.classLength = h

    def nu(self, k):
        """get begin moment"""
        return sum(map(lambda v : v[1]*v[0]**k, self.variant))/self.dimension

    def mu(self, k):
        """get central moment"""
        nu1 = self.nu(1)
        return sum(map(lambda v : v[1]*(v[0] - nu1)**k, self.variant))/self.dimension

    def distributing(self, x):
        """distributing function based on variant"""
        if not self.variant or x < self.front:
            return 0
        elif x >= self.back:
            return 1
        else:
            p = int((x - self.front)/self.classLength)
            return self.variant[p][3]

    def closeness(self, x):
        """closeness function based on variant"""
        if not self.variant or x < self.front or x >= self.back:
            return 0
        else:
            p = int((x - self.front)/self.classLength)
            return self.variant[p][1]

